iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 13
1
Software Development

Go繁不及備載系列 第 13

# Day13 Go怎麼傳不過去-指標的小用處(Pointer)

  • 分享至 

  • xImage
  •  

Day13 Go怎麼傳不過去-指標的小用處(Pointer)

指標

【Pointer指標】

在Go語言中的 pointer:指標、指針,
可以指向var 變數 的記憶體位置,或指到struct 物件本身
基本上用法跟C語言大同小異

& 取變數的位址
* 取變數的數值

宣告指標的方式

var Variable *Type

var a int = 10
var b *int = &a

https://play.golang.org/p/grTH_rokhvO

func main() {
	var a int = 10
	var b *int
	b = &a
	fmt.Println(a, b)

	var c string = "hi"
	var d *string
	d = &c
	*d = "who"	// 透過`*向址取值`的方式來改變變數裡面的內容值。  
	fmt.Println(c, d)
}

/* result:
10 0xc00000a108
who 0xc0000401f0
*/

變數若為int型態就需要用int指標來指
反之亦然

以下是一連串的取值、取址練習

https://play.golang.org/p/LnxluzPOPPL

func main() {
	x := 1
	p := &x          //p(type: *int)指向x
	fmt.Println(x)   //1
	fmt.Println(p)   //p指向的位址
	fmt.Println(*p)  //p指向的位址的值,意即變數x
	fmt.Println(&p)  //p本身的位址
	y := &p          //y存放了 p本身的位址
	fmt.Println(**y) //到y取值(到p本身的位址取值) 再取值,意即變數x
	**y = 100
	fmt.Println(x)
}

/* result:
1
0xc00011e090
1
0xc000148018
1
100
*/

【Swap Function交換數值】

Go是直接對Value值做操作的,
除非傳入func的參數是Pointer指標位址,才會對位址的Value值進行操作。

這邊以不用return回傳值的方式,來做交換值範例:

func main() {
	a, b := 10, 20
	swap(a, b)
	fmt.Println(a, b)
}

// 我以為有用的 swap function
func swap(a int, b int) {
	temp := a
	a = b
	b = temp
}

/*
10 20
*/

swap func可沒有偷懶,確實有把交換的動作做到定位(可以印出來看),
但這邊交換值不成,是因為交換到了func中宣告的值

實際上若要交換值返回,需要把位址傳入、直接對位址內的值進行操作

// 實際上真正有用的 Swap function
func Swap(a *int, b *int) {
	//fmt.Println(a, b) //0xc00000a108 0xc00000a120
	temp := *a
	*a = *b
	*b = temp
}

這範例雖然簡短,
但建議還是要自己寫過一遍、從頭思考Try一次,
對什麼時候該放*、什麼時候該放&,比較不會感到那麼困惑。

【Struct物件上的 Pointer】

在物件結構struct上也會遇到類似的問題,
這邊以昨天的笨貓肥貓當作範例,
笨貓二號今天難得變聰明了,想要主人讓他改名:

https://play.golang.org/p/q_9Hg41ZA1Y

type Cat struct {
	catName string
}

func (c Cat) eat() {
	fmt.Println("貓貓", c.catName, "開動哩")
}

func (c Cat) rename(newName string) {
	c.catName = newName
}

func main() {
	var cat1 = Cat{"肥貓一號"}
	cat1.eat()

	var cat2 = Cat{"笨貓二號"}
	cat2.rename("聰明貓三號") // 奇怪,怎麼改名失敗了
	cat2.eat()
}

/* result:
貓貓 肥貓一號 開動哩
貓貓 笨貓二號 開動哩
*/

笨貓二號心想:
疑?奇怪,怎麼改名失敗了
難道是因為我沒有克金?

糞game,不玩了

究竟什麼樣的原因導致傳不過去的問題發生事實擺在眼前呢?

經過一番辛苦努力和輕鬆課金之後,
笨貓二號成了課長...

func (c *Cat) rename(newName string) {
	c.catName = newName
}

func main() {
	var cat1 = &Cat{"肥貓一號"}
	cat1.eat()

	var cat2 = &Cat{"笨貓二號"}
	cat2.rename("課長貓")
	cat2.eat()
}
/* result:
貓貓 肥貓一號 開動哩
貓貓 課長貓 開動哩
*/

https://play.golang.org/p/_sP-s9q9knj

這邊改名成功是因為傳了物件的位址進去,讓func rename對該物件改位址上的值,
如果不是傳位址的話,func rename會將值copy一份到自己的範圍底下開心的改值,
改完但還沒沒有傳出來就消失了。

當我們看到cat2.rename("聰明貓三號")的呼叫函式並傳入引數、
再往回看func rename,將程式碼一掃而過都會覺得沒問題。

func (c Cat) rename(newName string) {
	c.catName = newName
}

但是當物件呼叫了方法,輸出卻不是預期的結果,這種情況也不失為一個【小坑】。
為了以防這種狀況,通常在寫物件的方法時 會把物件的位址傳入。

有一種說法是在func內傳遞Pointer指針的效率會比傳入物件來的高,
因為func不用copy整個物件結構的值。
但實際上的情況詳見評論


【New Func】

透過Struct物件Pointer,我們可以自製New Ojb Func,這個func回傳被實體化物件的指標
相當於用New產生一個物件。

https://play.golang.org/p/49BcBvE2Cgg

type cat struct {
	name string
}

func main() {
	var c = &cat{name: "始祖貓"}
	fmt.Println(c, &c)

	n1 := newCat("")
	n2 := newCat("複製貓三號")

	fmt.Println(n1, &n1)
	fmt.Println(n2, &n2)

	var c2 = new(cat) // 內建的new方法
	fmt.Println(c2, &c2)
}

func newCat(n string) *cat {
	return &cat{name: n}
}

/* result:
&{始祖貓} 0xc0000ca018
&{} 0xc0000ca028
&{複製貓三號} 0xc0000ca030
&{} 0xc0000ca038
*/

看到這,對什麼時候該用指針還有些疑惑嗎,
推薦看看這篇文件指導


上一篇
# Day12 Go神奇的介面與斷言(Interface, assertion)
下一篇
# Day14 Go併發症狀- Goroutines (go)
系列文
Go繁不及備載35
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言